package org.goldenport.g3.adapters

import org.goldenport.util.QSymbol
import org.goldenport.g3._
import org.goldenport.g3.events.{Exceptioned, G3RollforwardResults, G3BesteffortResults, RecordFetched, RecordQueried}
import org.goldenport.g3.events.entity._
import org.goldenport.g3.messages._
import org.goldenport.g3.messages.query.{RecordQuery, IdPattern, ForwardIncludeSlot, BackwardIncludeSlot, FieldSlot, AllFieldSlot}
import org.goldenport.g3.messages.entity._

/**
 * @since   Apr. 11, 2010
 * @version Jan. 11, 2011
 * @author  ASAMI, Tomoharu
 */
class EntityAdapter(val driver: G3EntityDriver) extends G3Adapter {
  override def open_Adapter() {
    driver.open(context)
  }

  override def close_Adapter() {
    driver.close()
  }

  override protected def do_Action(content: AnyRef, parcel: G3Parcel, ctx: G3AgentContext): AnyRef = {
    content match {
      case cmd: G3Command => driver.execute(cmd, ctx)
/*
      case create: CreateEntityClass => {
        driver.create(create)
      }
      case read: ReadRecord => {
        driver.read(read)
      }
      case read: ReadRecordSet => {
        driver.read(read)
      }
      case insert: InsertRecord => {
        driver.insert(insert)
      }
      case insert: InsertRecordSet => {
        driver.insert(insert)
      }
      case update: UpdateRecord => {
        driver.update(update)
      }
      case update: UpdateRecordSet => {
        driver.update(update)
      }
      case delete: DeleteRecord => {
        driver.delete(delete)
      }
      case delete: DeleteRecordSet => {
        driver.delete(delete)
      }
      case drop: DropEntityClass => {
        driver.drop(drop)
      }
      case get: Get => {
        driver.get(uri, get, ctx)
      }
      case post: Post => {
        driver.post(uri, post, ctx)
      }
      case put: Put => {
        driver.put(uri, put, ctx)
      }
      case delete: Delete => {
        driver.delete(uri, delete, ctx)
      }
      case _ => {
        error("???:" + content)
      }
*/
    }
  }
}

abstract class G3EntityDriver extends G3Driver {
  protected def new_Agent(cmd: G3Command, ctx: G3AgentContext): G3EntityDriverAgent
}

/*
abstract class G3EntityDriver extends G3Driver {
  protected def new_Agent(uri: String, ctx: G3AgentContext): G3EntityDriverAgent

  def get(uri: String, get: Get, ctx: G3AgentContext) = {
    val agent = new_Agent(uri, ctx)
    agent.open()
    try {
      agent.get(get)
    } finally {
      agent.close()
    }
  }

  def post(uri: String, post: Post, ctx: G3AgentContext) {
    val agent = new_Agent(uri, ctx)
    agent.open()
    try {
      agent.postTx(post)
      agent.postRollforward(post)
      agent.postBesteffort(post)
    } finally {
      agent.close()
    }
  }

  def put(uri: String, put: Put, ctx: G3AgentContext) {
    val agent = new_Agent(uri, ctx)
    agent.open()
    try {
      agent.putTx(put)
      agent.putRollforward(put)
      agent.putBesteffort(put)
    } finally {
      agent.close()
    }
  }

  def delete(uri: String, delete: Delete, ctx: G3AgentContext) {
    val agent = new_Agent(uri, ctx)
    agent.open()
    try {
      agent.deleteTx(delete)
      agent.deleteRollforward(delete)
      agent.deleteBesteffort(delete)
    } finally {
      agent.close()
    }
  }
}
*/

abstract class G3EntityDriverAgent(uri: String, context: G3AgentContext) extends G3DriverAgent(uri, context) with UseRecord {

  agent {
    case cmd: CreateEntityClass => create(cmd)
    case cmd: FetchEntity => fetch(cmd)
    case cmd: QueryEntity => query(cmd)
    case cmd: QueryEntityExpression => query(cmd)
    case cmd: InsertEntity => insert(cmd)
    case cmd: InsertEntitySet => insert(cmd)
    case cmd: UpdateEntity => update(cmd)
    case cmd: UpdateEntitySet => update(cmd)
    case cmd: DeleteEntityId => delete(cmd)
    case cmd: DeleteEntityExample => delete(cmd)
    case cmd: DeleteEntityExpression => delete(cmd)
    case cmd: DropEntityClass => drop(cmd)
  }

  def create(msg: CreateEntityClass): G3Event = {
    createTx(msg) match {
      case e: Exceptioned => e
      case evt: G3Event => {
        val rfs = createRollforward(msg)
        val bes = createBesteffort(msg)
        new EntityCreated(uri, rfs, bes)
      }
    }
  }

  def createTx(msg: CreateEntityClass): G3Event

  def createRollforward(msg: CreateEntityClass): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def createBesteffort(msg: CreateEntityClass): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def fetch(msg: FetchEntity): G3Event = {
    fetchTx(msg) match {
      case e: Exceptioned => e
      case evt: RecordFetched => new EntityFetched(uri, msg, evt.records, evt.single, Some(evt))
      case evt: RecordQueried => new EntityFetched(uri, msg, evt.records, false, Some(evt))
    }
  }

  def fetchTx(msg: FetchEntity): G3Event

  def query(msg: QueryEntity): G3Event = {
    queryTx(msg) match {
      case e: Exceptioned => e
      case evt: RecordQueried => new EntityQueried(uri, msg, evt.records, Some(evt))
    }
  }

  def queryTx(msg: QueryEntity): G3Event

  def query(msg: QueryEntityExpression): G3Event = {
    queryTx(msg) match {
      case e: Exceptioned => e
      case evt: RecordQueried => new EntityQueried(uri, msg, evt.records, Some(evt))
    }
  }

  def queryTx(msg: QueryEntityExpression): G3Event

  def insert(msg: InsertEntity): G3Event = {
    insertTx(msg) match {
      case e: Exceptioned => e
      case evt: G3Event => {
        val rfs = insertRollforward(msg)
        val bes = insertBesteffort(msg)
        new EntityInserted(uri, rfs, bes)
      }
    }
  }

  def insertTx(msg: InsertEntity): G3Event

  def insertRollforward(msg: InsertEntity): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def insertBesteffort(msg: InsertEntity): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def insert(msg: InsertEntitySet): G3Event = {
    insertTx(msg) match {
      case e: Exceptioned => e
      case evt: G3Event => {
        val rfs = insertRollforward(msg)
        val bes = insertBesteffort(msg)
        new EntityInserted(uri, rfs, bes)
      }
    }
  }

  def insertTx(msg: InsertEntitySet): G3Event

  def insertRollforward(msg: InsertEntitySet): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def insertBesteffort(msg: InsertEntitySet): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def update(msg: UpdateEntity): G3Event = {
    updateTx(msg) match {
      case e: Exceptioned => e
      case evt: G3Event => {
        val rfs = updateRollforward(msg)
        val bes = updateBesteffort(msg)
        new EntityUpdated(uri, rfs, bes)
      }
    }
  }

  def updateTx(msg: UpdateEntity): G3Event

  def updateRollforward(msg: UpdateEntity): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def updateBesteffort(msg: UpdateEntity): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def update(msg: UpdateEntitySet): G3Event = {
    updateTx(msg) match {
      case e: Exceptioned => e
      case evt: G3Event => {
        val rfs = updateRollforward(msg)
        val bes = updateBesteffort(msg)
        new EntityUpdated(uri, rfs, bes)
      }
    }
  }

  def updateTx(msg: UpdateEntitySet): G3Event

  def updateRollforward(msg: UpdateEntitySet): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def updateBesteffort(msg: UpdateEntitySet): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def delete(msg: DeleteEntityId): G3Event = {
    deleteTx(msg) match {
      case e: Exceptioned => e
      case evt: G3Event => {
        val rfs = deleteRollforward(msg)
        val bes = deleteBesteffort(msg)
        new EntityDeleted(uri, rfs, bes)
      }
    }
  }

  def deleteTx(msg: DeleteEntityId): G3Event

  def deleteRollforward(msg: DeleteEntityId): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def deleteBesteffort(msg: DeleteEntityId): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def delete(msg: DeleteEntityExample): G3Event = {
    deleteTx(msg) match {
      case e: Exceptioned => e
      case evt: G3Event => {
        val rfs = deleteRollforward(msg)
        val bes = deleteBesteffort(msg)
        new EntityDeleted(uri, rfs, bes)
      }
    }
  }

  def deleteTx(msg: DeleteEntityExample): G3Event

  def deleteRollforward(msg: DeleteEntityExample): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def deleteBesteffort(msg: DeleteEntityExample): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def delete(msg: DeleteEntityExpression): G3Event = {
    deleteTx(msg) match {
      case e: Exceptioned => e
      case evt: G3Event => {
        val rfs = deleteRollforward(msg)
        val bes = deleteBesteffort(msg)
        new EntityDeleted(uri, rfs, bes)
      }
    }
  }

  def deleteTx(msg: DeleteEntityExpression): G3Event

  def deleteRollforward(msg: DeleteEntityExpression): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def deleteBesteffort(msg: DeleteEntityExpression): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def drop(msg: DropEntityClass): G3Event = {
    dropTx(msg) match {
      case e: Exceptioned => e
      case evt: G3Event => {
        val rfs = dropRollforward(msg)
        val bes = dropBesteffort(msg)
        new EntityDropped(uri, rfs, bes)
      }
    }
  }

  def dropTx(msg: DropEntityClass): G3Event

  def dropRollforward(msg: DropEntityClass): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def dropBesteffort(msg: DropEntityClass): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def get(get: Get): G3Event = {
    error("")
  }

  def post(post: Post): G3Event = {
    error("")
  }

  def put(put: Put): G3Event = {
    error("")
  }

  def delete(delete: Delete): G3Event = {
    error("")
  }

  def mkcol(mkcol: Mkcol): G3Event = {
    error("")
  }

/*
  def post(post: Post): G3Event = {
    postTx(post)
    val rfs = postRollforward(post)
    val bes = postBesteffort(post)
    new EntityPosted(uri, rfs, bes)
  }

  def put(put: Put): G3Event = {
    putTx(put)
    val rfs = putRollforward(put)
    val bes = putBesteffort(put)
    new EntityPutted(uri, rfs, bes)
  }

  def delete(delete: Delete): G3Event = {
    deleteTx(delete)
    val rfs = deleteRollforward(delete)
    val bes = deleteBesteffort(delete)
    new EntityDeleted(uri, rfs, bes)
  }

  def mkcol(mkcol: Mkcol): G3Event = {
    mkcolTx(mkcol)
    val rfs = mkcolRollforward(mkcol)
    val bes = mkcolBesteffort(mkcol)
    new EntityMkcoled(uri, rfs, bes)
  }

  def postTx(post: Post)
  def postRollforward(post: Post): G3RollforwardResults
  def postBesteffort(post: Post): G3BesteffortResults
  def putTx(put: Put)
  def putRollforward(put: Put): G3RollforwardResults
  def putBesteffort(put: Put): G3BesteffortResults
  def deleteTx(delete: Delete)
  def deleteRollforward(delete: Delete): G3RollforwardResults
  def deleteBesteffort(delete: Delete): G3BesteffortResults
  def mkcolTx(mkcol: Mkcol)
  def mkcolRollforward(mkcol: Mkcol): G3RollforwardResults
  def mkcolBesteffort(mkcol: Mkcol): G3BesteffortResults
*/
}

/*
trait G3TxEntityDriverAgent extends G3EntityDriverAgent {
  def postRollforward(post: Post): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def postBesteffort(post: Post): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def putRollforward(put: Put): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def putBesteffort(put: Put): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def deleteRollforward(delete: Delete): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def deleteBesteffort(delete: Delete): G3BesteffortResults = {
    new G3BesteffortResults()
  }

  def mkcolRollforward(mkcol: Mkcol): G3RollforwardResults = {
    new G3RollforwardResults()
  }

  def mkcolBesteffort(mkcol: Mkcol): G3BesteffortResults = {
    new G3BesteffortResults()
  }
}
*/

/*
class DataStoreEntityDriver(val uri: String) extends G3EntityDriver {
  protected def new_Agent(cmd: G3Command, ctx: G3AgentContext): G3EntityDriverAgent = {
    new DataStoreEntityDriverAgent(ctx.createDataStoreDriver(uri), uri, ctx)
  }
}
*/

abstract class G3DataStoreEntityDriverAgent(val datastoreUri: String, context: G3AgentContext) extends G3EntityDriverAgent(datastoreUri, context) {
  lazy val dsdriver = context.createDataStoreDriver(datastoreUri)

  val linkCachePolicy: LinkCacheConstraint = CLinkCache
  val linkHoldPolicy: LinkHoldConstraint = CLinkHold // XXX
  val instancePolicy: InstanceConstraint = CPhysicalUpdateDelete // XXX

  val entityName: QSymbol
  val schema: RecordSchema

  private def record_class = new RecordClass(entityName, schema)
  private def entity_uri = entityName

  override def open() {
    dsdriver.open(context.context)
  }

  override def close() {
  }

  def createTx(msg: CreateEntityClass): G3Event = {
    dsdriver.execute(datastore.Create(record_class), context)
  }

  def fetchTx(msg: FetchEntity): G3Event = {
    val query = make_fetch_query(entity_uri, msg.ids)
    dsdriver.execute(datastore.Query(query), context)
  }

  private def field_to_slot(field: RecordField) = {
    field match {
      case Field(name, datatype, multiplicity, constraints, facets, properties) if constraints.exists(_.isInstanceOf[CDerivation]) => {
        ForwardIncludeSlot(name)(entity_uri, constraints.find(_.isInstanceOf[CDerivation]).get.asInstanceOf[CDerivation])
      }
      case Field(name, datatype, multiplicity, constraints, facets, properties) => {
        FieldSlot(entity_uri, name)
      }
    }
  }

  private def make_fetch_query(uri: QSymbol, ids: List[AnyRef]): RecordQuery = {
    RecordQuery(
      schema.fields.flatMap {
        case Field(name, datatype, multiplicity, constraints, facets, properties) if constraints.exists(_.isInstanceOf[CId]) => {
          List(IdPattern(uri, name, ids))
        }
        case _ => Nil
      }.toList,
      schema.fields.map(field_to_slot).toList
    )
  }

  def queryTx(msg: QueryEntity): G3Event = {
//    val record = Record('author_name -> "Yamada Taro")
    val query = merge_query(msg.query)
    dsdriver.execute(datastore.Query(query), context)
  }

  private def merge_query(q: RecordQuery): RecordQuery = {
    def merged_patterns = {
      q.patterns // XXX
    }

    def merged_slots = {
      q.slots.flatMap {
        case f: ForwardIncludeSlot => List(f)
        case b: BackwardIncludeSlot => List(b)
        case f: FieldSlot => List(f)
        case a: AllFieldSlot => schema.fields.map(field_to_slot)
      }
    }

    new RecordQuery(merged_patterns, merged_slots, q.context)
  }

  // XXX
  def queryTx(msg: QueryEntityExpression): G3Event = {
//    val expr = Attr('author_name) === "Yamada Taro"
    dsdriver.execute(datastore.Query(entity_uri, msg.expression), context)
  }

  def insertTx(msg: InsertEntity): G3Event = {
    val record = normalize_record_insert(msg.record)
    dsdriver.execute(datastore.Insert(entity_uri, record), context)
  }

  def insertTx(msg: InsertEntitySet): G3Event = {
    val records = normalize_records_insert(msg.records)
    dsdriver.execute(datastore.Insert(entity_uri, records), context)
  }

  def updateTx(msg: UpdateEntity): G3Event = {
    val record = normalize_record_update(msg.record)
    dsdriver.execute(datastore.Update(entity_uri, record), context)
  }

  def updateTx(msg: UpdateEntitySet): G3Event = {
    val records = normalize_records_update(msg.records)
    dsdriver.execute(datastore.Update(entity_uri, records), context)
  }

  def deleteTx(msg: DeleteEntityId): G3Event = {
    dsdriver.execute(datastore.Delete(entity_uri, msg.ids), context)
  }

  def deleteTx(msg: DeleteEntityExample): G3Event = {
    dsdriver.execute(datastore.Delete(entity_uri, msg.record), context)
  }

  def deleteTx(msg: DeleteEntityExpression): G3Event = {
    dsdriver.execute(datastore.Delete(entity_uri, msg.expression), context)
  }

  def dropTx(msg: DropEntityClass): G3Event = {
    dsdriver.execute(datastore.Drop(entity_uri), context)
  }

/*
  def get(get: Get) = {
    datastore.execute(get, context)
    error("")
  }

  def postTx(post: Post) = {
    error("")
    
  }

  def putTx(put: Put) = {
    error("")
    
  }

  def deleteTx(delete: Delete) = {
    error("")
    
  }

  def mkcolTx(mkcol: Mkcol) = {
    error("")
    
  }
*/

  private def normalize_record_insert(in: Record): Record = {
    normalize_record(in)
  }

  private def normalize_record_update(in: Record): Record = {
    normalize_record(in)
  }

  // normalizes record for insert and update
  private def normalize_record(in: Record): Record = {
    linkCachePolicy match {
      case CLinkNoCache => normalize_record_nocache(in)
      case CLinkCache => normalize_record_cache(in)
    }
  }

  // uses join dynamically to read data from external table.
  // insert and updates ignores derivation columns
  private def normalize_record_nocache(in: Record): Record = {
    val record = new Record()
    for ((key, value) <- in) {
      value match {
        case None => record += key -> value
        case c: CId => error("XXX")
        case c: CUnique => error("XXX")
        case c: CDerivation => ;
        case _ => record += key -> value
      }
    }
    record
  }

  private def normalize_record_cache(in: Record): Record = {
    val record = new Record()
    for ((key, value) <- in) {
      value match {
        case None => record += key -> value
        case c: CId => error("XXX")
        case c: CUnique => error("XXX")
        case c: CDerivation => record += key -> value
        case _ => record += key -> value
      }
    }
    record
  }

  private def normalize_records_insert(in: RecordSet): RecordSet = {
    new TransformRecordSet(in, context.context)(normalize_record_insert _)
  }

  private def normalize_records_update(in: RecordSet): RecordSet = {
    new TransformRecordSet(in, context.context)(normalize_record_update _)
  }
}
